/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.common.spec.grammar.io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;

import org.ourgrid.common.spec.grammar.Grammar;
import org.ourgrid.common.spec.grammar.Rule;
import org.ourgrid.common.spec.grammar.Symbol;
import org.ourgrid.common.spec.grammar.exception.InvalidRuleException;

/**
 * This is a GrammarReader entity that knows how to read a file generated by the
 * Gals software (version 2003.10.03)
 * 
 * @see Gals - http://sourceforge.net/projects/gals/
 */
public class GalsGrammarReader implements GrammarReader {

	/**
	 * This symbol is the one that is recognized as the empty rule at the
	 * grammar description language. In this case is the GALS grammar
	 * description language.
	 */
	private final String NULL_SYMBOL = "" + '@';

	// OBS.: This symbols was modified because we have got problems using the
	// original '?' symbol.
	// Theses problems are related with the codifications at the remote machines
	// that do not support
	// this symbol... then, to use our grammars into GALS, change this symbol.

	private BufferedReader reader;

	private Grammar grammar;

	private int ruleCounter;

	private int symbolCounter;


	/**
	 * @see org.ourgrid.common.spec.grammar.io.GrammarReader#read(java.io.File,
	 *      org.ourgrid.common.spec.grammar.Grammar)
	 */
	public Grammar read( File grammarFile, Grammar toFillGrammar ) throws MalformedGrammarException,
		FileNotFoundException, IOException {

		this.reader = new BufferedReader( new FileReader( grammarFile ) );
		buildGrammar( toFillGrammar );
		return this.grammar;
	}


	/**
	 * @see org.ourgrid.common.spec.grammar.io.GrammarReader#read(java.io.InputStream,
	 *      org.ourgrid.common.spec.grammar.Grammar)
	 */
	public Grammar read( InputStream stream, Grammar toFillGrammar ) throws MalformedGrammarException,
		FileNotFoundException, IOException {

		this.reader = new BufferedReader( new InputStreamReader( stream ) );
		buildGrammar( toFillGrammar );
		return this.grammar;
	}


	/**
	 * @param toFillGrammar The grammar object to be filled with the read
	 *        informations
	 * @throws IOException
	 * @throws MalformedGrammarException
	 */
	private void buildGrammar( Grammar toFillGrammar ) throws IOException, MalformedGrammarException {

		this.grammar = toFillGrammar;
		this.ruleCounter = 0;
		this.symbolCounter = 0;

		this.getGrammar();
	}


	/**
	 * Changes the grammar passed as paramether for the read() method. It will
	 * fill up the grammar with the information read from the file.
	 * 
	 * @throws MalformedGrammarException
	 */
	private void getGrammar() throws IOException, MalformedGrammarException {

		String actualLine = null;
		StringBuffer buffer = null;

		actualLine = this.readNonBlankLine();
		if ( actualLine != null ) {
			buffer = new StringBuffer( actualLine );
			String blockName = (buffer.substring( 1 )).trim();
			// First Block of needed informatin from the Gals grammar
			// description file.
			while ( !blockName.equals( "Tokens" ) && !blockName.equals( "" ) ) {
				blockName = this.getNextBlockName();
			}
			blockName = this.getTerminalInformations();

			// Second Block of needed informatin from the Gals grammar
			// description file.
			blockName = this.getNonTerminalInformations();

			// Third Block of needed informatin from the Gals grammar
			// description file.
			this.getRulesInformations();

		} else {
			throw new MalformedGrammarException( "The grammar file is empty." );
		}

	}


	/**
	 * Will read the informations from the grammar block and insert the
	 * necessary ones at the grammar.
	 * 
	 * @throws MalformedGrammarException
	 */
	private void getRulesInformations() throws IOException, MalformedGrammarException {

		String line = this.readNonBlankLine();
		while ( line != null ) {
			StringTokenizer headAndBodies = new StringTokenizer( line, " " );
			String headStr = headAndBodies.nextToken().trim();
			Symbol head = this.createSymbol( ++this.symbolCounter, headStr, Symbol.NON_TERMINAL );
			if ( headAndBodies.nextToken().equals( "::=" ) ) {
				Vector<Symbol> bodyVector = new Vector<Symbol>();
				String symbolStr = "";
				while ( head != null ) {
					if ( headAndBodies.hasMoreTokens() ) {
						symbolStr = headAndBodies.nextToken().trim();
					} else { // the line ended and was not found a ";"
								// symbol. It is necessary to get next line
						headAndBodies = this.getNextNonEmptyTokenizer();
						symbolStr = headAndBodies.nextToken().trim();
					}
					if ( symbolStr.equals( "|" ) ) { // Time to close a rule
														// with the actual head
														// and bodyVector
						createAndDeployRule( ++ruleCounter, head, bodyVector );
						bodyVector = new Vector<Symbol>();
					} else {
						if ( symbolStr.equals( ";" ) ) { // Time to eliminate
															// the actual head
							createAndDeployRule( ++ruleCounter, head, bodyVector );
							bodyVector = new Vector<Symbol>();
							head = null;
						} else { // Read another symbol from token and add it
									// at bodyVector
							Symbol symbol;
							if ( symbolStr.charAt( 0 ) == '<' ) {
								symbol = this.createSymbol( ++this.symbolCounter, symbolStr, Symbol.NON_TERMINAL );
							} else if ( symbolStr.charAt( 0 ) == '#' ) {
								String action = symbolStr.trim();
								symbol = new Symbol( Integer.MAX_VALUE, action, Symbol.SEMANTIC_ACTION );
							} else {
								symbol = this.createSymbol( ++this.symbolCounter, symbolStr, Symbol.TERMINAL );
							}
							bodyVector.add( symbol );
						}
					}
				}

			} else { // Error situation
				throw new MalformedGrammarException( "There is a rule without a head or the symbol \"::=\" was forgot." );
			}
			line = this.readNonBlankLine();
		}

	}


	private String readNonBlankLine() throws IOException {

		String line = "";
		do {
			line = reader.readLine();
			if ( line == null )
				break;
		} while ( line.equals( "" ) );
		return line;
	}


	/*
	 * Search for the next line non empty and returns a StringTokenizer of this
	 * line.
	 */
	private StringTokenizer getNextNonEmptyTokenizer() throws IOException {

		String line = this.readNonBlankLine();
		return new StringTokenizer( line, " " );
	}


	private Symbol createSymbol( int code, String value, int type ) {

		// If the value is in format "xxx" will eliminate the " symbols
		int last = value.length() - 1;
		if ( value.charAt( 0 ) == '\"' && value.charAt( last ) == '\"' ) {
			StringBuffer buffer = new StringBuffer( value );
			value = buffer.substring( 1, last );
		}
		Symbol toTest = grammar.getSymbol( value );
		if ( toTest == null ) {
			if ( value.equals( this.NULL_SYMBOL ) ) {
				toTest = Symbol.EMPTY;
			} else {
				toTest = new Symbol( code, value, type );
			}

		} else {
			--symbolCounter;
		}
		return toTest;
	}


	/*
	 * Creates a rule and add it at the grammar.
	 */
	private void createAndDeployRule( int codeCounter, Symbol head, Vector<Symbol> bodyVector )
		throws MalformedGrammarException {

		try {
			Rule rule = new Rule( codeCounter, head, getArrayOfSymbols( bodyVector ) );
			this.grammar.addRule( rule );
		} catch ( InvalidRuleException irex ) {
			Iterator<Symbol> it = bodyVector.iterator();
			String rule = head.getValue() + " ::= ";
			while ( it.hasNext() )
				rule = rule + (it.next()).getValue();
			throw new MalformedGrammarException( "Found a MalFormed rule : " + rule, irex );
		}
	}


	/*
	 * Transform a vector os Symbol objects in a array cointaining them.
	 */
	private Symbol[ ] getArrayOfSymbols( Vector<Symbol> vec ) {

		Symbol[ ] toReturn = new Symbol[ vec.size() ];
		Iterator<Symbol> it = vec.iterator();
		int counter = 0;
		while ( it.hasNext() ) {
			toReturn[counter++] = it.next();
		}

		return toReturn;
	}


	/**
	 * Will read the informations from the non terminals block and insert the
	 * necessaries ones at the grammar.
	 * 
	 * @return The last line read from the file. The empty string if occurres
	 *         any problem.
	 */
	private String getNonTerminalInformations() throws IOException {

		String newSymbol = this.readNonBlankLine();
		while ( newSymbol.charAt( 0 ) != '#' ) {
			Symbol symbol = this.createSymbol( ++this.symbolCounter, newSymbol.trim(), Symbol.NON_TERMINAL );
			this.grammar.addSymbol( symbol );
			newSymbol = this.readNonBlankLine();
		}
		StringBuffer buffer = new StringBuffer( newSymbol );
		return buffer.substring( 1 );
	}


	/**
	 * Will read the informations from the tokens block and insert the
	 * necessaries ones at the grammar.
	 * 
	 * @return The last line read from the file. The empty string if occurres
	 *         any problem.
	 */
	private String getTerminalInformations() throws IOException {

		String newSymbol = this.readNonBlankLine();
		while ( newSymbol.charAt( 0 ) != '#' ) {
			Symbol symbol = this.createSymbol( ++this.symbolCounter, newSymbol.trim(), Symbol.TERMINAL );
			this.grammar.addSymbol( symbol );
			newSymbol = this.readNonBlankLine();
		}
		StringBuffer buffer = new StringBuffer( newSymbol );
		return buffer.substring( 1 );
	}


	/**
	 * Will try to find another line that begins with the '#' symbol.
	 * 
	 * @return The name of the next block if it was found, and the empty string
	 *         if not.
	 * @throws MalformedGrammarException if the EOF was found
	 */
	private String getNextBlockName() throws IOException, MalformedGrammarException {

		String line = "";
		do {
			line = reader.readLine();
			if ( line != null ) {
				// Test if this line is empty because of the index above.
				if ( !line.equals( "" ) ) {
					if ( line.charAt( 0 ) == '#' ) {
						StringBuffer buffer = new StringBuffer( line );
						return buffer.substring( 1 ).trim();
					}
				}
			}
		} while ( line != null );

		throw new MalformedGrammarException( "The grammar have not all the informations block necessaries." );
	}

}
